(**

   Le  module Serveur permet  de réagir  aux messages  de {!Protocole}
   émis  par les  clients. Il  s'appuie sur  le module  {!Tarotv} pour
   représenter en interne les clients  et s'assurer de la validité des
   requêtes. Vous  devez vous occuper de  mettre en place  un moyen de
   communiquer avec  les clients vous-même, pour  fournir les methodes
   {!set_deconnecter}, {!nouveau_client}, et {!assimiler_message}.

   @author Vivien Kraus

*)

(**

   Type abstrait de représentation du serveur.

*)
type serveur

(** 

    Crée un  serveur vide. Attention  : pour pouvoir  l'utiliser, vous
    devez fournir 4 fonctions qui servent à :

    {ul
    {- suivre les états du client vu par le serveur,}
    {- être informé quand une partie est terminée,}
    {- envoyer un message à un certain client,}
    {- terminer la session d'un certain client.}}

    Dans  le  cas contraire,  vous  verrez  l'exception 
    [Failure  "Not specified"].

*)
val make: unit -> serveur
(* Si vous ne spécifiez pas le suiveur, la fonction envoyer et la fonction déconnecter,
Failure "Not specified" sera levée.*)

(**

   [set_suiveur  serveur fonction]  appellera  la fonction  [fonction]
   chaque fois  que l'état  d'un client aura  changé. Par  exemple, la
   fonction :
   
   [fun nom msg cause -> 
   Printf.printf "L'état du joueur %s a changé, car il a reçu %s. %s\n%!" 
   nom msg cause] 
   
   est un bon candidat.

*)
val set_suiveur: serveur -> (string -> string -> string -> unit) -> unit
(* à appeler sur nom du client avec la raison du changement en cas de changement d'état *)

(**

   [set_finisseur serveur  fonction] appellera la  fonction [fonction]
   chaque fois qu'une partie se terminera. Par exemple, la fonction :
   
   [fun noms -> 
   Printf.printf "Les joueurs suivants ont fini une partie : %s\n%!" 
   (String.concat ", " (Array.to_list noms))] 
   
   est un bon candidat.

*)
val set_finisseur: serveur -> (string array -> unit) -> unit

(**

   [set_envoyer  serveur fonction]  appellera  la fonction  [fonction]
   chaque fois  que le serveur aura  besoin d'envoyer un  message à un
   certain client. Attention : dès l'identification, il faudra envoyer
   des  messages au  nouveau client.  Vous devrez  donc impérativement
   tirer  profit de  la fonction  paramètre de  {!nouveau_client} pour
   mettre à jour votre dictionnaire.

*)
val set_envoyer: serveur -> (string -> Protocole.msg -> unit) -> unit
(* fonction pour envoyer un message *)

(**

   [set_deconnecter  serveur fonction]  appellera [fonction  client] à
   chaque fois que la connexion  virtuelle entre le client [client] et
   le serveur [serveur]  sera rompue, vous laissant le  soin de fermer
   concrètement les socket et mettre à jour votre dictionnaire.

*)
val set_deconnecter: serveur -> (string -> unit) -> unit
(* fonction pour déconnecter un joueur *)

(**

   [dump   serveur   joueur]   retourne   l'état  actuel   du   client
   [joueur]. Vous  pouvez utiliser cette fonction à  chaque fois qu'un
   changement d'état intéressant a  lieu, par exemple dans la fonction
   que nous avons ébauché en exemple pour {!set_suiveur}.

*)
val dump: serveur -> string -> Tarotv.etat

(**

   Retourne tous les noms utilisés.

*)
val noms: serveur -> string list (* noms utilisés *)

(**
   
   Parfois,  une  déconnexion imprévue  arrive.  Il  faut indiquer  au
   serveur  que  le  client  n'est  plus disponible.  L'effet  est  de
   déconnecter   tous  les   membres  de   la  partie,   ou  d'envoyer
   {!Protocole.Sortie} si  le joueur n'était  pas en jeu.  Attention :
   lorsque vous déconnectez  un client, il est très  probable que vous
   détectiez une  déconnexion de ce  même client juste après.  Si vous
   enregistrez  deux fois la  déconnexion d'un  même client,  seule la
   première aura un effet.

*)
val enregistrer_deconnexion: serveur -> string -> unit (* Traite le cas d'une déconnexion impromptue d'un client, en déconnectant les membres de la partie ou en informant les autres joueurs en attente qu'il n'est plus disponible. Équivaut à assimiler message avec Protocole.Deconnexion. *)

(**
   
   Crée  un nouveau  joueur  à  partir du  premier  message reçu  d'un
   nouveau  client. Il  y a  deux fonctions  en argument.  La première
   vérifie que ce nom peut  être pris. La seconde valide l'utilisation
   d'un nom,  en utilisant un  argument de type [Some  nom_valide], ou
   signale un échec de la procédure, avec l'argument [None]. Attention
   : en cas de réussite, dès  la fin de l'appel à la deuxième fonction
   argument,  le  serveur  va   envoyer  des  messages  à  ce  nouveau
   client. C'est donc le seul endroit pour ajouter ce nouveau client à
   votre dictionnaire des sockets.

*)
val nouveau_client: serveur -> (string -> bool) -> (string option -> unit) -> Protocole.msg -> unit
(* Vérifie si un client peut s'identifier avec ce message de protocole. Si la place est libre, on appelle quand même la première fonction pour voir si le nom n'est pas réservé. Si tout est bon, on valide officiellement l'identification en appelant la deuxième fonction avec comme argument le nom retenu, avant de finir de traiter la connexion (entrées). *)

(**
   
   À chaque fois  qu'un client de nom [client]  émet un message [msg],
   vous devez l'assimiler.  Les réponses aux uns et  aux autres seront
   automatiquement calculées et envoyées.

*)
val assimiler_message: serveur -> string -> Protocole.msg -> unit
  (* réagit. *)
